package cz.semjob.document.annotation.reader;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import cz.semjob.document.annotation.AnnotatedPart;
import cz.semjob.document.annotation.DocumentAnnotations;
import cz.semjob.document.annotation.EntityAnnotation;
import cz.semjob.document.annotation.EventAnnotation;
import cz.semjob.document.annotation.RelationAnnotation;
import cz.semjob.document.annotation.metadata.AnnotationTypeFactory;
import cz.semjob.document.annotation.metadata.Entity;
import cz.semjob.document.annotation.metadata.Relation;

@Component
public class AnnotationReader {

	private static Logger logger = Logger.getLogger(AnnotationReader.class);

	@Autowired
	private AnnotationTypeFactory annotationTypeFactory;

	private AnnotationLineStringComparator lineComparator = new AnnotationLineStringComparator();

	public DocumentAnnotations readAnnotatedDocument(File annotationFile)
			throws AnnotationReaderException {
		if (!annotationFile.exists()) {
			throw new AnnotationReaderException(
					"Annotation file does not exist");
		}
		logger.debug("Processing file: " + annotationFile.getAbsolutePath());
		List<String> lines;
		try {
			lines = FileUtils.readLines(annotationFile);
		} catch (IOException e) {
			logger.error(e);
			throw new AnnotationReaderException(e);
		}
		// map annotation id, annotation data
		Map<String, EntityAnnotation> entities = new HashMap<String, EntityAnnotation>();
		Map<String, RelationAnnotation> relations = new HashMap<String, RelationAnnotation>();
		Map<String, EventAnnotation> events = new HashMap<String, EventAnnotation>();

		List<String> entityLines = new ArrayList<String>();
		List<String> eventLines = new ArrayList<String>();
		List<String> relationLines = new ArrayList<String>();
		List<String> symtranRelationLines = new ArrayList<String>();

		for (String line : lines) {
			if (line.length() > 0) {
				char firstChar = line.charAt(0);
				switch (firstChar) {
				case 'T': {
					entityLines.add(line);
					break;
				}
				case 'R': {
					relationLines.add(line);
					break;
				}
				case 'E': {
					eventLines.add(line);
					break;
				}
				case '*': {
					symtranRelationLines.add(line);
					break;
				}
				default: {
					// nothing
				}
				}
			}
		}
		Collections.sort(entityLines, lineComparator);
		for (String line : entityLines) {
			processEntity(line, entities);
		}
		Collections.sort(eventLines, lineComparator);
		for (String line : eventLines) {
			processEventEntities(line, events, entities);
		}
		for (String line : eventLines) {
			processEventArguments(line, events, entities, relations);
		}
		Collections.sort(relationLines, lineComparator);
		for (String line : relationLines) {
			processRelation(line, relations, entities, events);
		}

		for (String line : symtranRelationLines) {
			processSymTranRelation(line, relations, entities);
		}

		DocumentAnnotations annotatedDocument = new DocumentAnnotations(
				annotationTypeFactory);

		annotatedDocument.addEntities(entities.values());
		annotatedDocument.addRelations(relations.values());
		annotatedDocument.addEvents(events.values());
		logger.debug("Annotation reading end.");
		return annotatedDocument;
	}

	private void processEventEntities(String line,
			Map<String, EventAnnotation> events,
			Map<String, EntityAnnotation> entities)
			throws AnnotationReaderException {
		line = line.trim();
		logger.debug("Reading event from line: " + line);
		String[] words = line.split("[ \\t:]");
		if (words.length < 3) {
			throw new AnnotationReaderException("Line '" + line
					+ "' has wrong format");
		}
		String eventId = words[0];
		String eventType = words[1];
		String eventEntityId = words[2];
		// creating the annotation representing event, ex. znalost
		EntityAnnotation eventEntity = entities.get(eventEntityId);
		if (eventEntity == null) {
			throw new AnnotationReaderException(
					"Error while creating event:Event entity has not been defined in the annotation file");
		}
		EventAnnotation event = new EventAnnotation(
				annotationTypeFactory.getEntity(eventType), eventEntity);

		events.put(eventId, event);

	}

	public void processEventArguments(String line,
			Map<String, EventAnnotation> events,
			Map<String, EntityAnnotation> entities,
			Map<String, RelationAnnotation> relations)
			throws AnnotationReaderException {
		line = line.trim();
		String[] words = line.split("[ \\t:]");
		String eventId = words[0];
		EventAnnotation event = events.get(eventId);
		for (int i = 3; i < words.length;) {
			String argumentType = words[i];
			String argumentEntityId = words[i + 1];
			EntityAnnotation argumentEntity = entities.get(argumentEntityId);
			EventAnnotation argumentEvent = events.get(argumentEntityId);
			if (argumentEntity == null && argumentEvent == null) {
				throw new AnnotationReaderException(
						"Error while creating event:Argument event or entity have not been defined in the annotation file");
			}
			Relation eventRelation = annotationTypeFactory
					.getRelation(argumentType);
			if (argumentEntity != null) {
				event.addArgument(argumentType, argumentEntity);
				argumentEntity.addRelatedEvent(event);
				relations.put(eventId + argumentType, new RelationAnnotation(
						event.getEventEntity(), argumentEntity, eventRelation));

			} else {
				event.addArgument(argumentType, argumentEvent);
				relations.put(eventId + argumentType, new RelationAnnotation(
						event.getEventEntity(), argumentEvent.getEventEntity(),
						eventRelation));
			}
			i += 2;
		}
	}

	private void processRelation(String line,
			Map<String, RelationAnnotation> relations,
			Map<String, EntityAnnotation> entities,
			Map<String, EventAnnotation> events)
			throws AnnotationReaderException {
		line = line.trim();
		logger.debug("Reading relation from line: " + line);
		String[] words = line.split("[ \\t:]");
		if (words.length != 6) {
			throw new AnnotationReaderException("Line '" + line
					+ "' has wrong format");
		}
		String relationId = words[0];
		String relationType = words[1];
		String firstEntityId = words[3];
		String secondEntityId = words[5];
		
		if (entities.get(firstEntityId) == null && events.get(firstEntityId) == null) {
			logger.warn("Annotation not defined for entity: " + firstEntityId);
			return;
		}
		if (entities.get(secondEntityId) == null && events.get(secondEntityId) == null) {
			logger.warn("Annotation not defined for entity: " + secondEntityId);
			return;
		}
		
		EntityAnnotation entity1 = entities.get(firstEntityId);
		if (entity1 == null ) {
			entity1 = events.get(firstEntityId).getEventEntity();
		}
		EntityAnnotation entity2 = entities.get(secondEntityId);
		if (entity2 == null) {
			entity2 = events.get(secondEntityId).getEventEntity();
		}
		if (entity1 == null || entity2 == null) {
			throw new AnnotationReaderException(
					"Error while creating relation: One or more entities have not been defined in the annotation file");
		}

		Relation relation = annotationTypeFactory.getRelation(relationType);
		if (relation != null) {
			RelationAnnotation relationAnnotation = new RelationAnnotation(
					entity1, entity2, relation);
			relations.put(relationId, relationAnnotation);
		}

	}

	private void processSymTranRelation(String line,
			Map<String, RelationAnnotation> relations,
			Map<String, EntityAnnotation> entities)
			throws AnnotationReaderException {
		line = line.trim();
		logger.debug("Reading symmetric and transitive relation from line: "
				+ line);
		String[] words = line.split("[ \\t:]");
		if (words.length < 4) {
			throw new AnnotationReaderException("Line '" + line
					+ "' has wrong format");
		}
		String relationType = words[1];
		Relation relation = annotationTypeFactory.getRelation(relationType);
		if (relation != null) {
			List<EntityAnnotation> entityAnnotations = new ArrayList<EntityAnnotation>();
			for (int i = 2; i < words.length; i++) {
				String entityId = words[i];
				EntityAnnotation entityAnnotation = entities.get(entityId);
				if (entityId == null) {
					throw new AnnotationReaderException(
							"Error while creating relation: Entities has not been defined in the annotation file");
				}
				entityAnnotations.add(entityAnnotation);
			}

			EntityAnnotation previousEntityAnnotation = null;
			for (EntityAnnotation entityAnnotation : entityAnnotations) {
				if (previousEntityAnnotation != null) {
					RelationAnnotation relationAnnotation = new RelationAnnotation(
							previousEntityAnnotation, entityAnnotation,
							relation);
					// we do not have id, so we put line as id
					relations.put(line, relationAnnotation);
				}
				previousEntityAnnotation = entityAnnotation;

			}
		}

	}

	private void processEntity(String line,
			Map<String, EntityAnnotation> entities)
			throws AnnotationReaderException {
		line = line.trim();
		logger.debug("Reading entity from line: " + line);
		String[] words = line.split("[ \\t;]");
		if (words.length < 4) {
			throw new AnnotationReaderException("Line '" + line
					+ "' has wrong format");
		}
		String entityId = words[0];
		String entityType = words[1];
		String text = "";
		
		Entity entity = annotationTypeFactory.getEntity(entityType);
		if (entity == null) {
			return;
		}
		EntityAnnotation entityAnnotation = new EntityAnnotation(
				entity);
		boolean rangesParsed = false;
		for (int i = 2; i < words.length;) {
			if (!rangesParsed && parseNumber(words[i]) != null) {
				int startIndex = parseNumber(words[i]);
				int endIndex = parseNumber(words[i + 1]);
				AnnotatedPart part = new AnnotatedPart(startIndex, endIndex);
				entityAnnotation.addAnnotatedPart(part);
				int currentParsingIndex = line.indexOf(words[i+1]) + words[i+1].length();
				if (line.charAt(currentParsingIndex) !=';') {
					rangesParsed = true;
				} else {
					//System.out.println();
				}
				i += 2;
			} else {
				text += words[i] + " ";
				i += 1;
			}

		}
		if (text.length() < 2) {
			throw new AnnotationReaderException("Line '" + line
					+ "' has wrong format");
		}
		text = text.substring(0, text.length() - 1);
		entityAnnotation.setText(text);
		entities.put(entityId, entityAnnotation);
	}

	private Integer parseNumber(String word) {
		Integer number = null;
		try {
			number = Integer.valueOf(word);
		} catch (NumberFormatException e) {

		}
		return number;
	}

	public void setAnnotationTypeFactory(
			AnnotationTypeFactory annotationTypeFactory) {
		this.annotationTypeFactory = annotationTypeFactory;
	}

}
